<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>
  <script>
    function isObject(value) {
      var type = typeof value;
      return value != null && (type == 'object' || type == 'function');
    }

    var now = function() {
      return Date.now();
    };

    function debounce(func, wait, options = {}) {
      var lastArgs,
          lastThis,
          maxWait,
          result,
          timerId,
          lastCallTime,
          lastInvokeTime = 0,
          leading = false,
          maxing = false,
          trailing = true
      
      if (typeof func !== 'function') {
        throw new TypeError('这不是一个函数')
      }
      // wait = toNumber(wait) || 0
      wait = Number(wait) || 0
      if (isObject(options)) {
        leading = !!options.leading
        maxing = 'maxWait' in options
        // maxWait = maxing ? Math.max(toNumber(options.maxWait) || 0, wait) : maxWait
        maxWait = maxing ? Math.max(Number(options.maxWait) || 0, wait) : maxWait
        trailing = 'trailing' in options ? !!options.trailing : trailing // 默认trailing为true
      }

      function invokeFunc(time) {
        // 直接运行方法，返回结果
        var args = lastArgs,
            thisArg = lastThis
        lastArgs = lastThis = undefined
        lastInvokeTime = time
        result = func.apply(thisArg, args)
        console.log('===invokeFunc===', result)
        return result
      }

      function leadingEdge(time) {
        // 第一次运行，如果开启了leading会先直接返回一次结果，并且上面同时开启定时任务
        // 这个方法只会在第一次运行
        lastInvokeTime = time
        timerId = setTimeout(timerExpired, wait)
        return leading ? invokeFunc(time) : result
      }

      function remainingWait(time) {
        var timeSinceLastCell = time - lastCallTime, // 上次触发到现在的时间
            timeSinceLastInvoke = time - lastInvokeTime, // 上次获取结果到现在的时间
            timeWaiting = wait - timeSinceLastCell // 等待触发时间
        console.log('===remainingWait===', timeWaiting, maxWait - timeSinceLastInvoke)
        // 如果设置了maxWait，则取最小值
        // 因为加了maxWait后，
        return maxing ? Math.min(timeWaiting, maxWait - timeSinceLastInvoke) : timeWaiting

      }

      function shouldInvoke(time) {
        // lastCallTime是debounce方法最后一次运行的时间
        // timeSinceLastCell 最后一次高频操作到现在的时间差
        // timeSinceLastInvoke 上一次获取结果到现在的时间差
        var timeSinceLastCell = time - lastCallTime,
            timeSinceLastInvoke = time - lastInvokeTime
        // console.log('shouldInvoke timeSinceLastCell 最后一次高频操作到现在的时间差', timeSinceLastCell)
        // console.log('shouldInvoke timeSinceLastInvoke 最后一次获取结果到现在的时间差', timeSinceLastInvoke)
        
        // lastCallTime === undefined 第一次运行
        // timeSinceLastCell >= wait 最后一次高频操作到现在的时间差 大于或等于 wait延迟时间
        // timeSinceLastCell < 0 一般来说time大于lastCallTime，但是有可能系统时间被修改，小于lastCallTime，这种情况当做trailingEdge
        // maxing && timeSinceLastInvoke >= maxWait 上一次获取结果到现在的时间差 大于或等于 maxWait
        var isOk = (lastCallTime === undefined || (timeSinceLastCell >= wait) || (timeSinceLastCell < 0) || (maxing && timeSinceLastInvoke >= maxWait))
        console.log('===shouldInvoke===', lastCallTime === undefined, timeSinceLastCell >= wait, timeSinceLastCell < 0, (maxing && timeSinceLastInvoke >= maxWait), isOk ? 'isInvokin = true' : 'isInvoking = false')
        return (lastCallTime === undefined || (timeSinceLastCell >= wait) || (timeSinceLastCell < 0) || (maxing && timeSinceLastInvoke >= maxWait))
      }

      function timerExpired() {
        // 运行到这里表明上一个setTimeout已经触发
        var time = now()
        // console.log('timerExpired', time)
        // 每次都判断我当前是否可以获取结果，只要满足就不会再有其他的setTimeout，也就是当前用户没有高频操作了
        if (shouldInvoke(time)) {
          return trailingEdge(time)
        }
        var remaingWaitTime = remainingWait(time)
        console.log('timerExpired remainingWait', remaingWaitTime)
        timerId = setTimeout(timerExpired, remaingWaitTime)
      }

      function trailingEdge(time) {
        timerId = undefined
        if (trailing && lastArgs) {
          console.log('===trailingEdge trailing = true===', lastArgs)
          return invokeFunc(time)
        }
        lastArgs = lastThis = undefined
        console.log('===trailingEdge trailing = false===', result)
        return result
      }

      function cancel() {
        // 取消延迟调用
        if (timerId !== undefined) {
          clearTimeout(timerId)
        }
        lastInvokeTime = 0
        lastArgs = lastCallTime = lastThis = timerId = undefined
      }

      function flush() {
        // 立即调用
        return timerId === undefined ? result : trailingEdge(now())
      }

      function debounced() {
        var time = now(),
            isInvoking = shouldInvoke(time)
        // 获取参数
        lastArgs = arguments
        // 获取this
        lastThis = this
        // 在先运行shouldInvoke(time)，在保存当前time到lastCallTime
        lastCallTime = time
        if (isInvoking) {
          // 第一次运行时 isInvoking 为true, 这里直接返回 leadingEdge
          if (timerId === undefined) {
            return leadingEdge(lastCallTime)
          }
          if (maxing) {
            // 最大延迟时间开启，一般maxTime大于wait。
            // 这里会再次设定定时任务，并立即返回一次结果。
            // 如果有一种场景，高频操作如果一直没有停下，那就永远看不到最近的运行结果，
            // 而添加了maxTime之后，即使一直在高频操作，程序也会在最大延迟时间触发一下运行结果。
            // 为什么能够触发这次最大延迟的操作呢？就是通过 isInvoking 标志
            
            clearTimeout(timerId);
            timerId = setTimeout(timerExpired, wait)
            console.log('debounced maxing And timerId = ', timerId)
            return invokeFunc(lastCallTime)
          }
        }
        // 如果一直在操作，当maxWait的最大延迟已经运行一次，这里会
        if (timerId === undefined) {
          timerId = setTimeout(timerExpired, wait)
          console.log('debounced timerId === undefined And timerId = ', timerId)
        }
        return result
      }

      debounced.cancel = cancel
      debounced.flush = flush
      return debounced
    }

    function throttle(func, wait, options) {
      var leading = true,
          trailing = true
      
      if (typeof func != 'function') {
        throw new TypeError('Expected a function')
      }

      if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading
        trailing = 'trailing' in options ? !!options.trailing : trailing
      }

      return bebounce(func, wait, {
        'leading': leading,
        'maxWait': wait,
        'trailing': trailing
      })
    }
    
    var resCount = 0
    var mmFunc = debounce((e) => {
      resCount += 1
      console.log('mousemove Event', resCount)
      return resCount
    }, 1000, {
      // leading: true, // true表示第一次一定会先运行函数
      // maxWait: 3000, // 不管怎么样，每隔maxWait一定会运行一次函数
      trailing: false // true表示最后不再运行函数，false只返回上一次的结果
    })
    document.addEventListener('mousemove', mmFunc)
  </script>
</body>

</html>